package com.google.code.jbauble.dao.mysql;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.code.jbauble.dao.Datastore;
import com.google.code.jbauble.dao.Key;
import com.google.code.jbauble.dao.Query;
import com.google.code.jbauble.dao.SqlNative;
import com.google.code.jbauble.dao.annotations.Entity;
import com.google.code.jbauble.dao.mapping.MappedClass;
import com.google.code.jbauble.dao.mapping.MappedField;
import com.google.code.jbauble.dao.mapping.Mapper;
import com.google.code.jbauble.exception.DataAccessException;
import com.google.code.jbauble.exception.MappingException;
import com.google.code.jbauble.utils.ReflectionUtils;

public class MysqlDatastore implements Datastore {
	protected MysqlNative jdbc = null;
	protected Mapper mapper = null;
	public MysqlDatastore(MysqlNative jdbc, String[] packages) {
		this.jdbc = jdbc;
		mapper = new Mapper();
		init(packages); 
	}

	private void init(String[] packages) {
		if (packages == null || packages.length < 1)
			return;
		
		for (String p : packages) {
			try {
				mapPackage(p);
			} catch (MappingException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void mapPackage(String packageName) throws MappingException {
		try {
			Set<Class<?>> classes = ReflectionUtils.getClasses(packageName);
            for (Class c : classes) {
            	mapper.addMappedClass(c);
            }
        } catch (IOException ioex) {
            throw new MappingException("Could not get map classes from package " + packageName, ioex);
        } catch (ClassNotFoundException cnfex) {
            throw new MappingException("Could not get map classes from package " + packageName, cnfex);
        }
	}

	public <T> Query<T> createQuery(Class<T> clazz) {
		return new MysqlQuery<T>(clazz, jdbc, this);
	}

	public <T> Query<T> createQuery(String kind, Class<T> clazz) {
		return new MysqlQuery<T>(clazz, jdbc, this);
	}

	public <T> Key<T> insert(T t) throws DataAccessException {
		MappedClass mc = mapper.getMappedClass(t);
		if (mc == null)
			throw new IllegalArgumentException();
		
		StringBuilder sql = new StringBuilder(); 
		sql.append("insert into " + mc.getCollectionName() + "(");
		Object[] param = getInsertFieldsAndValues(t, mc);
		sql.append(param[0].toString());
		sql.append(") values(");
		sql.append(param[1].toString());
		sql.append(")");
		
		Object id = jdbc.executeInsert(sql.toString(), toObjects((List)param[2]));
		if (id != null) {
			try {
				mc.getIdField().set(t, id);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return new Key(t.getClass(), id);
	}

	private Object[] toObjects(List valueList) {
		int count = valueList.size();
		Object[] objs = new Object[count];
		for (int i=0; i<count; i++) {
			objs[i] = valueList.get(i);
		}
		return objs;
	}

	private Object[] getInsertFieldsAndValues(Object t, MappedClass mc) {
		Field idField = mc.getIdField();
		List<MappedField> fs = mc.getPersistenceFields();
		String fields = "";
		String values = "";
		List objs = new ArrayList();
		for (MappedField f : fs) {
			if (!isSingleValue(f) || f.getFieldValue(t) == null)
				continue;
			
			if (f.getJavaFieldName().toLowerCase().equals(idField.getName().toLowerCase()))
				continue;
			
			fields += "`"+f.getJavaFieldName() + "`,";
			values += "?,";
			/*if (f.getType().equals(String.class) || f.getType().equals(Date.class)) {
				values += "?,";
			}*/
			objs.add(f.getFieldValue(t));
		}
		
		if (fields.length() > 0) {
			fields = fields.substring(0, fields.length() - 1);
			values = values.substring(0, values.length() - 1);
		}
		
		return new Object[]{fields, values, objs};
	}
	
	private boolean isSingleValue(MappedField f) {
		if (f.getType().equals(String.class) || f.getType().equals(int.class) 
				|| f.getType().equals(Integer.class) || f.getType().equals(Long.class)
				|| f.getType().equals(long.class) || f.getType().equals(Date.class)
				|| f.getType().equals(float.class) || f.getType().equals(Float.class)
				|| f.getType().equals(short.class) || f.getType().equals(Short.class)
				|| f.getType().equals(Boolean.class) || f.getType().equals(boolean.class))
			return true;
		return false;
	}
	
	@Override
	public <T> Key<T> save(T t) {
		MappedClass mc = mapper.getMappedClass(t);
		if (mc == null)
			throw new IllegalArgumentException();
		
		Object id = null;
		try {
			id = mc.getIdField().get(t);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Key<T> key = null;
		try {
			if (id == null) {
				id = insert(t);
				key = new Key(t.getClass(), id);
			}
			else {
				int updated = update(t);
				if (updated > 0) {
					key = new Key(t.getClass(), id);
				}
			}
		} catch (DataAccessException e) {
			e.printStackTrace();
			return null;
		}
		
		return key;
	}
	
	private <T> int update(T t) throws DataAccessException {
		MappedClass mc = mapper.getMappedClass(t);
		if (mc == null)
			throw new IllegalArgumentException();
		
		StringBuilder sql = new StringBuilder(); 
		sql.append("update " + mc.getCollectionName() + " set ");
		Object[] param = getUpdateFields(t, mc);
		sql.append(param[0]);
		sql.append(" where ");
		sql.append(param[1]);
		
		return jdbc.execute(sql.toString(), toObjects((List)param[2]));
	}
	
	private Object[] getUpdateFields(Object t, MappedClass mc) {
		Field idField = mc.getIdField();
		List<MappedField> fs = mc.getPersistenceFields();
		String fields = "";
		String idFieldString = "";
		try {
			idFieldString = idField.getName() + "=" + idField.get(t);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		List objs = new ArrayList();
		for (MappedField f : fs) {
			if (!isSingleValue(f))
				continue;
			
			if (f.getJavaFieldName().toLowerCase().equals(idField.getName().toLowerCase()))
				continue;
			
			/*if (f.getType().equals(String.class) || f.getType().equals(Date.class)) {
				fields += f.getJavaFieldName() + "=" + "?,";
			}*/
			fields += "`" + f.getJavaFieldName() + "`=" + "?,";
			objs.add(f.getFieldValue(t));
		}
		
		if (fields.length() > 0) {
			fields = fields.substring(0, fields.length() - 1);
		}
		
		return new Object[]{fields, idFieldString, objs};
	}
	
	@Override
	public <T> int delete(T t) {
		MappedClass mc = mapper.getMappedClass(t);
		if (mc == null)
			throw new IllegalArgumentException();
		
		StringBuilder sql = new StringBuilder(); 
		sql.append("delete from " + mc.getCollectionName() + " where ");
		Object[] param = getDeleteFields(t, mc);
		sql.append(param[0]);
		
		try {
			return jdbc.execute(sql.toString(), toObjects((List)param[1]));
		} catch (DataAccessException e) {
			e.printStackTrace();
			return 0;
		}
	}
	
	private Object[] getDeleteFields(Object t, MappedClass mc) {
		Field idField = mc.getIdField();
		List<MappedField> fs = mc.getPersistenceFields();
		String idFieldString = "";
		List objs = new ArrayList();
		try {
			idFieldString = idField.getName() + "=?";
			objs.add(idField.get(t));
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return new Object[]{idFieldString, objs};
	}

	@Override
	public <T> int delete(Query<T> q) {
		MappedClass mc = mapper.getMappedClass(q.getEntityClass());
		if (mc == null)
			throw new IllegalArgumentException();
		
		MysqlQuery query = (MysqlQuery)q;
		StringBuilder sql = new StringBuilder(); 
		sql.append("delete from ");
		sql.append(mc.getCollectionName());
		String fields = query.getFilterString();
		Object[] values = query.getFilterObjects();
		if (fields != null && !fields.equals("")) {
			sql.append(" where ");
			sql.append(fields);
		}
		
		try {
			return jdbc.execute(sql.toString(), values);
		} catch (DataAccessException e) {
			e.printStackTrace();
			return 0;
		}
	}

	@Override
	public <T> Iterable<Key<T>> save(Iterable<T> ts) {
		if (ts == null)
			return null;
		
		List<Key<T>> list = new ArrayList();
		for (T t : ts) {
			list.add(save(t));
		}
		return list;
	}

	@Override
	public <T> Iterable<Key<T>> save(T... ts) {
		if (ts == null)
			return null;
		
		List<Key<T>> list = new ArrayList();
		for (T t : ts) {
			list.add(save(t));
		}
		return list;
	}

	private Object[] merge(Object[] fields, Object[] whereObjects) {
		int count = 0;
		if (fields != null && fields.length > 0) {
			count = fields.length;
		}
		if (whereObjects != null && whereObjects.length > 0) {
			count += whereObjects.length;
		}
		
		if (count < 1)
			return null;
		
		Object[] objs = new Object[count];
		int index = 0;
		if (fields != null && fields.length > 0) {
			for (int i=0; i<fields.length; i++) {
				objs[index++] = fields[i];
			}
		}
		if (whereObjects != null && whereObjects.length > 0) {
			for (int i=0; i<whereObjects.length; i++) {
				objs[index++] = whereObjects[i];
			}
		}
		return objs;
	}
	
	public <T, V> Query<T> get(Class<T> clazz, Iterable<V> ids) {
		return find(clazz).disableValidation().filter(Mapper.ID_KEY + " in", ids).enableValidation();
	}
	
	public <T> Query<T> find(String kind, Class<T> clazz) {
		return createQuery(clazz);
	}
	

	public <T> Query<T> find(Class<T> clazz) {
		return createQuery(clazz);
	}
	

	public <T, V> Query<T> find(Class<T> clazz, String property, V value) {
		Query<T> query = createQuery(clazz);
		return query.filter(property, value);
	}
	

	public <T, V> Query<T> find(String kind, Class<T> clazz, String property, V value, int offset, int size) {
		return find(kind, clazz, property, value, offset, size, true);
	}
	
	public <T, V> Query<T> find(String kind, Class<T> clazz, String property, V value, int offset, int size,
			boolean validate) {
		Query<T> query = find(kind, clazz);
		if (!validate)
			query.disableValidation();
		query.offset(offset);
		query.limit(size);
		return query.filter(property, value).enableValidation();
	}
	

	public <T, V> Query<T> find(Class<T> clazz, String property, V value, int offset, int size) {
		Query<T> query = createQuery(clazz);
		query.offset(offset);
		query.limit(size);
		return query.filter(property, value);
	}

	public Mapper getMapper() {
		return mapper;
	}

	@Override
	public <T> Key<T> getKey(T entity) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Key<?> exists(Object keyOrEntity) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public <T, V> int delete(Class<T> clazz, V id) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public <T, V> int delete(Class<T> clazz, Iterable<V> ids) {
		// TODO Auto-generated method stub
		return 0;
	}
	
	public SqlNative getSqlNative() {
		return jdbc;
	}
}
